/* 
 * File:   CalculatorParser.cpp
 * Author: Gab'z
 * 
 * Created on 27 mai 2012, 18:18
 */

#include "CalculatorParser.h"
#include <cmath>
#include "../tool/trim.h"

namespace calculator {
    Logger* CalculatorParser::log = LoggerManager::getInstance()->getLogger("CalculatorParser");

    CalculatorParser::CalculatorParser(const Context * const context) :
    _context(context) {
    }

    CalculatorParser::~CalculatorParser() {
    }

    bool CalculatorParser::isInteger(const std::string& str) const {
        log->trace("isInteger ? " + str);
        const char* c = str.c_str();
        for (unsigned int i = 0; i < str.length(); i++, c++) {
            if (!(*c >= '0' && *c <= '9')) return false;
        }
        return true;
    }

    bool CalculatorParser::isRationnal(const std::string& str, int* pos) const {
        log->trace("isRationnal ?");
        if (str.length() < 3) return false;
        // au moins un caractère avant un / d'où le 1 dans la comparaison
        std::size_t position = str.find("/", 1);
        if (position == str.npos) return false;
        // la partie rationnelle fait au moins 1 caractère
        if (str.length() - int(position) < 1) return false;
        if (!(isInteger(str.substr(0, position))
                && isInteger(str.substr(int(position) + 1, str.length() - int(position))))
                ) return false;
        // les 2 valeurs sont bien des entiers
        if (pos != 0) *pos = int(position);
        return true;
    }

    bool CalculatorParser::isReal(const std::string& str, int* pos) const {
        log->trace("isReal ?");
        // au moins un caractère avant un . d'où le 1 dans la comparaison
        std::size_t position = str.find(".", 1);
        if (position == str.npos) return false;
        // la partie décimale fait au moins 1 caractère
        if (str.length() - int(position) < 1) return false;
        if (!(isInteger(str.substr(0, position))
                && isInteger(str.substr(int(position) + 1, str.length() - int(position))))
                ) return false;
        // les 2 valeurs sont bien des entiers
        if (pos != 0) *pos = int(position);
        return true;
    }

    bool CalculatorParser::isComplex(const std::string& str, int* pos) const {
        log->trace("isComplex ?" + str);
        // au moins un caractère avant un $ d'où le 1 dans la comparaison
        std::size_t position = str.find('$', 1);
        if (position == str.npos) return false;
        log->debug("$ PAS A LA FIN, ok");
        std::ostringstream oss;
        oss << "position = " << position;
        log->debug(oss.str());
        if (position < 1) return false;
        log->debug("$ PAS AU DEBUT, ok");
        // la partie imaginaire fait au moins 1 caractère
        //if (str.length() - *pos - 1 < 1) return false;
        if (!(isSimpleNumber(str.substr(0, position))
                && isSimpleNumber(str.substr(position + 1, str.length() - position)))
                ) return false;
        // les 2 valeurs sont bien des NombreSimple
        if (pos != 0) *pos = int(position);
        return true;
    }

    bool CalculatorParser::isSimpleNumber(const std::string& str) const {
        log->trace("isSimpleNumber ?");
        if (isInteger(str)
                || isRationnal(str)
                || isReal(str)) {
            return true;
        }
        return false;
    }

    bool CalculatorParser::isCommand(const std::string& str) const {
        log->trace("isCommand ?");
        return _context->getCommandMap()->isCommandName(str);
    }

    bool CalculatorParser::isExpression(const std::string& str) const {
        log->trace("isExpression ? " + str);
        if (str.length() < 3) return false;
        const char* c = str.c_str();
        if (c[str.length() - 1] != '\'') return false;
        log->trace("isExpression ? espace au début et à la fin, ok");
        // ou ne contenant que des espaces ou un autre caractère '
        bool onlySpaces = true;
        for (int i = 1; i < ((int) str.length() - 1); i++) {
            if (c[i] != ' ') onlySpaces = false;
            // pas d'expression à l'intérieur d'une expression
            if (c[i] == '\'') return false;
        }
        if (onlySpaces) return false;
        return true;
    }

    const Command* CalculatorParser::parse(const std::string& str) const throw (ParseException) {
        log->trace("parsing");
        // si string vide ou contient espace => Echec
        if (str.empty()) throw ParseException("Parsing empty input");

        const char* c = str.c_str();

        if ((*c >= '0') && (*c <= '9')) {
            // on tente d'en faire soit : entier, rationnel, reel, complexe
            if (isInteger(str)) {
                return makeInteger(str);
            }
            int position = 0;
            int* pos = &position;
            if (isRationnal(str, pos)) {
                return makeRationnal(str, pos);
            }
            if (isReal(str, pos)) {
                return makeReal(str, pos);
            }
            if (isComplex(str, pos)) {
                return makeComplex(str, pos);
            }
            std::string error = "Not a number : ";
            throw ParseException(error + str);
        } else if (*c == '\'') {
            if (isExpression(str)) {
                return makeExpression(str);
            }
        } else if (isCommand(str)) {
            return makeCommand(str);
        }

        std::string error = "Not parsable : ";
        throw ParseException(error + str);

    }

    const Integer* CalculatorParser::makeInteger(const std::string& str) const {
        log->trace("makeInteger");
        std::istringstream iss(str);
        // convertir string en un int puis Entier
        int nombre;
        iss >> nombre;
        return new Integer(nombre);
    }

    const Rationnal* CalculatorParser::makeRationnal(const std::string& str, int* pos) const {
        log->trace("makeRationnal");
        int num;
        int denom;
        std::istringstream iss(str.substr(0, *pos));
        iss >> num;
        std::istringstream iss2(str.substr(*pos + 1, str.length() - *pos));
        iss2 >> denom;
        return new Rationnal(num, denom);
    }

    const Real* CalculatorParser::makeReal(const std::string& str, int* pos) const {
        log->trace("makeReal");
        int pEntiere;
        int pDecimale;
        std::istringstream iss(str.substr(0, *pos));
        iss >> pEntiere;
        std::istringstream iss2(str.substr(*pos + 1, str.length() - *pos));
        iss2 >> pDecimale;
        return new Real(pEntiere + (double) pDecimale / std::pow(10, str.length() - *pos - 1));
    }

    const SimpleNumber* CalculatorParser::makeSimpleNumber(const std::string& str) const throw (ParseException) {
        log->trace("makeSimpleNumber");
        if (isInteger(str)) return makeInteger(str);
        int position = 0;
        int* pos = &position;
        if (isRationnal(str, pos)) return makeRationnal(str, pos);
        if (isReal(str, pos)) return makeReal(str, pos);
        std::string error = "Not a simple number : ";
        throw ParseException(error + str);
    }

    const Complex* CalculatorParser::makeComplex(const std::string& str, int* pos) const throw (ParseException) {
        log->trace("makeComplex");
        const SimpleNumber* sn1;
        const SimpleNumber* sn2;
        try {
            sn1 = makeSimpleNumber(str.substr(0, *pos));
        } catch (ParseException& e) {
            std::string error = "Not a complex : ";
            throw ParseException(error + str + " -> " + e.what());
        }
        try {
            sn2 = makeSimpleNumber(str.substr(*pos + 1, str.length() - *pos));
        } catch (ParseException& e) {
            delete sn1;
            std::string error = "Not a complex : ";
            throw ParseException(error + str + " -> " + e.what());
        }
        const Complex* c = new Complex(sn1, sn2);
        delete sn1;
        delete sn2;
        return c;
    }

    const Command* CalculatorParser::makeCommand(const std::string& str) const throw (ParseException) {
        log->trace("makeCommand");
        const Command* command;
        try {
            command = _context->getCommandMap()->getCommand(str);
        } catch (CommandException& e) {
            std::string error = "Not a command : ";
            throw ParseException(error + str);
        }
        return command;
    }

    const Expression* CalculatorParser::makeExpression(const std::string& str) const throw (ParseException) {
        std::list<const Command*> commandList;


        std::string expressionString = str.substr(1, str.length() - 2);
        while (!expressionString.empty()) {
            std::string commandWord = "";


            // ISOLEMENT de la 1ère commande
            trim(expressionString);

            // recherche d'un mot terminé soit par la fin de la ligne,
            // soit par un espace
            std::size_t spacePosition = expressionString.find(" ");
            if (spacePosition != std::string::npos) {
                // espace trouvé on prend le mot
                commandWord = expressionString.substr(0, spacePosition);
            } else {
                // pas d'espace on prend toute la ligne
                commandWord = expressionString;
            }

            // PARSING 
            const Command* command;
            try {
                command = parse(commandWord);
            } catch (ParseException& e) {
                // échec parsing
                for (std::list<const Command*>::const_iterator it = commandList.begin(); it != commandList.end(); it++) {
                    delete *it;
                }
                commandList.clear();
                throw;
            }

            // AJOUT DE LA COMMANDE A LA LISTE
            commandList.push_back(command);

            // MODIFICATION DE LA STRING A TRADUIRE
            expressionString = expressionString.substr(commandWord.size());

        }

        //CREATION DE L'EXPRESSION (copiera les commandes)
        Expression* expression = new Expression(*(commandList.begin()));
        std::list<const Command*>::const_iterator it = commandList.begin();
        it++;
        for (; it != commandList.end(); it++) {
            expression->append(*it);
        }
        // SUPPRESSION DE LA LISTE LOCALE
        for (it = commandList.begin(); it != commandList.end(); it++) {
            delete *it;
        }
        commandList.clear();
        return expression;
    }


}